<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebRTC语音通话</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Arial', sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            color: white;
        }

        .container {
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            border-radius: 20px;
            padding: 30px;
            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);
            text-align: center;
            max-width: 500px;
            width: 90%;
        }

        h1 {
            margin-bottom: 30px;
            font-size: 2em;
            text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
        }

        .input-group {
            margin-bottom: 20px;
        }

        input {
            width: 100%;
            padding: 12px 20px;
            border: none;
            border-radius: 25px;
            font-size: 16px;
            text-align: center;
            background: rgba(255, 255, 255, 0.9);
            color: #333;
            outline: none;
            transition: all 0.3s ease;
        }

        input:focus {
            transform: scale(1.05);
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
        }

        .buttons {
            display: flex;
            gap: 15px;
            flex-wrap: wrap;
            justify-content: center;
            margin-bottom: 20px;
        }

        button {
            padding: 12px 25px;
            border: none;
            border-radius: 25px;
            font-size: 16px;
            cursor: pointer;
            transition: all 0.3s ease;
            font-weight: bold;
            min-width: 120px;
        }

        .primary {
            background: linear-gradient(45deg, #4CAF50, #45a049);
            color: white;
        }

        .secondary {
            background: linear-gradient(45deg, #2196F3, #1976D2);
            color: white;
        }

        .danger {
            background: linear-gradient(45deg, #f44336, #d32f2f);
            color: white;
        }

        button:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
        }

        button:disabled {
            opacity: 0.6;
            cursor: not-allowed;
            transform: none;
        }

        .status {
            background: rgba(255, 255, 255, 0.1);
            border-radius: 15px;
            padding: 15px;
            margin-top: 20px;
            min-height: 60px;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .status.connected {
            background: rgba(76, 175, 80, 0.2);
            border: 2px solid rgba(76, 175, 80, 0.5);
        }

        .status.calling {
            background: rgba(255, 193, 7, 0.2);
            border: 2px solid rgba(255, 193, 7, 0.5);
        }

        .users-list {
            background: rgba(255, 255, 255, 0.1);
            border-radius: 15px;
            padding: 15px;
            margin-top: 15px;
            text-align: left;
        }

        .user-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 8px 0;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        }

        .user-item:last-child {
            border-bottom: none;
        }

        .call-button {
            background: linear-gradient(45deg, #4CAF50, #45a049);
            color: white;
            border: none;
            padding: 6px 12px;
            border-radius: 15px;
            cursor: pointer;
            font-size: 12px;
        }

        .volume-meter {
            width: 100%;
            height: 20px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 10px;
            overflow: hidden;
            margin-top: 10px;
        }

        .volume-bar {
            height: 100%;
            background: linear-gradient(90deg, #4CAF50, #FFC107, #f44336);
            width: 0%;
            transition: width 0.1s ease;
        }

        @media (max-width: 600px) {
            .buttons {
                flex-direction: column;
                align-items: center;
            }
            
            button {
                width: 100%;
                max-width: 200px;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🎙️ WebRTC语音通话</h1>
        
        <div class="input-group">
            <input type="text" id="roomInput" placeholder="输入房间号 (例如: room123)" />
        </div>
        
        <div class="buttons">
            <button id="joinBtn" class="primary">加入房间</button>
            <button id="leaveBtn" class="danger" disabled>离开房间</button>
        </div>
        
        <div id="status" class="status">
            请输入房间号并加入房间开始语音通话
        </div>

        <div id="usersList" class="users-list" style="display: none;">
            <h3>房间用户:</h3>
            <div id="usersContainer"></div>
        </div>

        <div class="volume-meter" style="display: none;" id="volumeMeter">
            <div class="volume-bar" id="volumeBar"></div>
        </div>
    </div>

    <script src="/socket.io/socket.io.js"></script>
    <script>
        class VoiceChat {
            constructor() {
                this.socket = io();
                this.localStream = null;
                this.peerConnections = {};
                this.roomId = null;
                this.isJoined = false;
                
                this.initElements();
                this.setupEventListeners();
                this.setupSocketListeners();
                this.setupVolumeMonitoring();
            }

            initElements() {
                this.roomInput = document.getElementById('roomInput');
                this.joinBtn = document.getElementById('joinBtn');
                this.leaveBtn = document.getElementById('leaveBtn');
                this.status = document.getElementById('status');
                this.usersList = document.getElementById('usersList');
                this.usersContainer = document.getElementById('usersContainer');
                this.volumeMeter = document.getElementById('volumeMeter');
                this.volumeBar = document.getElementById('volumeBar');
            }

            setupEventListeners() {
                this.joinBtn.addEventListener('click', () => this.joinRoom());
                this.leaveBtn.addEventListener('click', () => this.leaveRoom());
                this.roomInput.addEventListener('keypress', (e) => {
                    if (e.key === 'Enter') this.joinRoom();
                });
            }

            setupSocketListeners() {
                this.socket.on('user-joined', (userId) => {
                    console.log('新用户加入:', userId);
                    this.createPeerConnection(userId, true);
                });

                this.socket.on('existing-users', (users) => {
                    console.log('现有用户:', users);
                    users.forEach(userId => {
                        this.createPeerConnection(userId, false);
                    });
                });

                this.socket.on('offer', async (data) => {
                    console.log('收到offer:', data.sender);
                    await this.handleOffer(data);
                });

                this.socket.on('answer', async (data) => {
                    console.log('收到answer:', data.sender);
                    await this.handleAnswer(data);
                });

                this.socket.on('ice-candidate', async (data) => {
                    console.log('收到ice-candidate:', data.sender);
                    await this.handleIceCandidate(data);
                });

                this.socket.on('user-left', (userId) => {
                    console.log('用户离开:', userId);
                    this.closePeerConnection(userId);
                    this.updateUsersList();
                });
            }

            setupVolumeMonitoring() {
                this.audioContext = null;
                this.analyser = null;
                this.microphone = null;
            }

            async getLocalStream() {
                try {
                    this.localStream = await navigator.mediaDevices.getUserMedia({
                        audio: {
                            echoCancellation: true,
                            noiseSuppression: true,
                            sampleRate: 44100
                        }
                    });
                    
                    this.setupVolumeMonitor();
                    return this.localStream;
                } catch (error) {
                    console.error('获取麦克风权限失败:', error);
                    this.updateStatus('无法获取麦克风权限，请允许麦克风访问', 'error');
                    throw error;
                }
            }

            setupVolumeMonitor() {
                try {
                    this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
                    this.analyser = this.audioContext.createAnalyser();
                    this.microphone = this.audioContext.createMediaStreamSource(this.localStream);
                    
                    this.analyser.fftSize = 512;
                    this.microphone.connect(this.analyser);
                    
                    this.volumeMeter.style.display = 'block';
                    this.monitorVolume();
                } catch (error) {
                    console.error('音量监控设置失败:', error);
                }
            }

            monitorVolume() {
                if (!this.analyser) return;
                
                const bufferLength = this.analyser.frequencyBinCount;
                const dataArray = new Uint8Array(bufferLength);
                
                const updateVolume = () => {
                    if (!this.isJoined) return;
                    
                    this.analyser.getByteFrequencyData(dataArray);
                    
                    let sum = 0;
                    for (let i = 0; i < bufferLength; i++) {
                        sum += dataArray[i];
                    }
                    const average = sum / bufferLength;
                    const percentage = (average / 255) * 100;
                    
                    this.volumeBar.style.width = percentage + '%';
                    
                    requestAnimationFrame(updateVolume);
                };
                
                updateVolume();
            }

            async joinRoom() {
                const roomId = this.roomInput.value.trim();
                if (!roomId) {
                    alert('请输入房间号');
                    return;
                }

                try {
                    await this.getLocalStream();
                    this.roomId = roomId;
                    this.socket.emit('join-room', roomId);
                    
                    this.isJoined = true;
                    this.joinBtn.disabled = true;
                    this.leaveBtn.disabled = false;
                    this.roomInput.disabled = true;
                    
                    this.updateStatus(`已加入房间: ${roomId}`, 'connected');
                    this.usersList.style.display = 'block';
                    
                } catch (error) {
                    console.error('加入房间失败:', error);
                }
            }

            leaveRoom() {
                this.isJoined = false;
                
                // 关闭所有peer连接
                Object.keys(this.peerConnections).forEach(userId => {
                    this.closePeerConnection(userId);
                });
                this.peerConnections = {};
                
                // 停止本地流
                if (this.localStream) {
                    this.localStream.getTracks().forEach(track => track.stop());
                    this.localStream = null;
                }
                
                // 停止音频上下文
                if (this.audioContext) {
                    this.audioContext.close();
                    this.audioContext = null;
                }
                
                this.socket.disconnect();
                this.socket.connect();
                
                this.joinBtn.disabled = false;
                this.leaveBtn.disabled = true;
                this.roomInput.disabled = false;
                this.roomInput.value = '';
                
                this.updateStatus('已离开房间');
                this.usersList.style.display = 'none';
                this.volumeMeter.style.display = 'none';
                this.usersContainer.innerHTML = '';
            }

            createPeerConnection(userId, isInitiator) {
                const configuration = {
                    iceServers: [
                        { urls: 'stun:stun1.l.google.com:19302' },
                        { urls: 'stun:stun2.l.google.com:19302' },
                        { urls: 'stun:stun3.l.google.com:19302' },
                        { urls: 'stun:stun4.l.google.com:19302' },
                        { urls: 'stun:23.21.150.121' },
                        { urls: 'stun:stun01.sipphone.com' },
                        { urls: 'stun:stun.ekiga.net' },
                        { urls: 'stun:stun.fwdnet.net' },
                        { urls: 'stun:stun.ideasip.com' },
                        { urls: 'stun:stun.iptel.org' },
                        { urls: 'stun:stun.rixtelecom.se' },
                        { urls: 'stun:stun.schlund.de' },
                        { urls: 'stun:stunserver.org' },
                        { urls: 'stun:stun.softjoys.com' },
                        { urls: 'stun:stun.voiparound.com' },
                        { urls: 'stun:stun.voipbuster.com' },
                        { urls: 'stun:stun.voipstunt.com' },
                        { urls: 'stun:stun.voxgratia.org' },
                        { urls: 'stun:stun.xten.com' }
                    ]
                };

                const peerConnection = new RTCPeerConnection(configuration);
                this.peerConnections[userId] = peerConnection;

                // 添加本地流
                if (this.localStream) {
                    this.localStream.getTracks().forEach(track => {
                        peerConnection.addTrack(track, this.localStream);
                    });
                }

                // 处理远程流
                peerConnection.ontrack = (event) => {
                    console.log('收到远程音频流:', userId);
                    this.playRemoteStream(event.streams[0], userId);
                };

                // ICE候选处理
                peerConnection.onicecandidate = (event) => {
                    if (event.candidate) {
                        this.socket.emit('ice-candidate', {
                            target: userId,
                            candidate: event.candidate
                        });
                    }
                };

                // 连接状态监控
                peerConnection.onconnectionstatechange = () => {
                    console.log(`与 ${userId} 的连接状态:`, peerConnection.connectionState);
                    if (peerConnection.connectionState === 'connected') {
                        this.updateStatus(`与用户 ${userId.substring(0, 8)} 语音连接已建立`, 'connected');
                    }
                };

                if (isInitiator) {
                    this.createOffer(userId);
                }

                this.updateUsersList();
            }

            async createOffer(userId) {
                try {
                    const peerConnection = this.peerConnections[userId];
                    const offer = await peerConnection.createOffer({
                        offerToReceiveAudio: true
                    });
                    
                    await peerConnection.setLocalDescription(offer);
                    
                    this.socket.emit('offer', {
                        target: userId,
                        sdp: offer
                    });
                } catch (error) {
                    console.error('创建offer失败:', error);
                }
            }

            async handleOffer(data) {
                try {
                    const peerConnection = this.peerConnections[data.sender];
                    if (!peerConnection) {
                        this.createPeerConnection(data.sender, false);
                    }
                    
                    await this.peerConnections[data.sender].setRemoteDescription(data.sdp);
                    
                    const answer = await this.peerConnections[data.sender].createAnswer();
                    await this.peerConnections[data.sender].setLocalDescription(answer);
                    
                    this.socket.emit('answer', {
                        target: data.sender,
                        sdp: answer
                    });
                } catch (error) {
                    console.error('处理offer失败:', error);
                }
            }

            async handleAnswer(data) {
                try {
                    const peerConnection = this.peerConnections[data.sender];
                    await peerConnection.setRemoteDescription(data.sdp);
                } catch (error) {
                    console.error('处理answer失败:', error);
                }
            }

            async handleIceCandidate(data) {
                try {
                    const peerConnection = this.peerConnections[data.sender];
                    if (peerConnection) {
                        await peerConnection.addIceCandidate(data.candidate);
                    }
                } catch (error) {
                    console.error('处理ICE候选失败:', error);
                }
            }

            playRemoteStream(stream, userId) {
                const audio = document.createElement('audio');
                audio.srcObject = stream;
                audio.autoplay = true;
                audio.controls = false;
                audio.id = `audio-${userId}`;
                document.body.appendChild(audio);
            }

            closePeerConnection(userId) {
                if (this.peerConnections[userId]) {
                    this.peerConnections[userId].close();
                    delete this.peerConnections[userId];
                }
                
                const audio = document.getElementById(`audio-${userId}`);
                if (audio) {
                    audio.remove();
                }
            }

            updateUsersList() {
                const connectedUsers = Object.keys(this.peerConnections);
                this.usersContainer.innerHTML = '';
                
                if (connectedUsers.length === 0) {
                    this.usersContainer.innerHTML = '<div style="text-align: center; opacity: 0.7;">等待其他用户加入...</div>';
                } else {
                    connectedUsers.forEach(userId => {
                        const userDiv = document.createElement('div');
                        userDiv.className = 'user-item';
                        userDiv.innerHTML = `
                            <span>🎤 用户 ${userId.substring(0, 8)}</span>
                            <span style="color: #4CAF50; font-size: 12px;">已连接</span>
                        `;
                        this.usersContainer.appendChild(userDiv);
                    });
                }
            }

            updateStatus(message, type = 'default') {
                this.status.textContent = message;
                this.status.className = `status ${type}`;
            }
        }

        // 初始化应用
        document.addEventListener('DOMContentLoaded', () => {
            new VoiceChat();
        });
    </script>
</body>
</html>